What is @fluentui/react-context-selector?
@fluentui/react-context-selector is a package that provides optimized context selectors for React. It allows you to create context providers and consumers with fine-grained updates, ensuring that only the components that need to re-render do so, improving performance in large applications.
What are @fluentui/react-context-selector's main functionalities?
Creating a Context
This feature allows you to create a new context using the `createContext` function provided by the package.
const MyContext = createContext(null);
Using a Context Provider
This feature allows you to create a context provider that supplies the context value to its children. The `useMemo` hook is used to memoize the context value to avoid unnecessary re-renders.
const MyProvider = ({ children }) => {
const value = useMemo(() => ({ /* some value */ }), []);
return <MyContext.Provider value={value}>{children}</MyContext.Provider>;
};
Consuming Context with Selector
This feature allows you to consume context values using the `useContextSelector` hook, which takes a context and a selector function. This ensures that the component only re-renders when the selected value changes.
const MyComponent = () => {
const selectedValue = useContextSelector(MyContext, context => context.someValue);
return <div>{selectedValue}</div>;
};
Other packages similar to @fluentui/react-context-selector
react-redux
react-redux is a package that provides bindings for using Redux with React. It allows you to connect your React components to the Redux store and select specific pieces of state. Compared to @fluentui/react-context-selector, react-redux is more focused on state management with Redux, while @fluentui/react-context-selector is focused on optimizing context usage in React.
recoil
recoil is a state management library for React that provides a way to manage global state with atoms and selectors. It allows you to create fine-grained state dependencies and optimize re-renders. Compared to @fluentui/react-context-selector, recoil offers a more comprehensive state management solution, while @fluentui/react-context-selector is specifically designed for optimizing context usage.
zustand
zustand is a small, fast, and scalable state management library for React. It allows you to create global state stores and use selectors to optimize re-renders. Compared to @fluentui/react-context-selector, zustand provides a more general state management solution, while @fluentui/react-context-selector focuses on optimizing context usage within React.
@fluentui/react-context-selector
React useContextSelector()
hook in userland.
Introduction
React Context and useContext()
is often used to avoid prop drilling,
however it's known that there's a performance issue. When a context value is changed, all components that are subscribed with useContext()
will re-render.
useContextSelector is recently proposed. While waiting for the process, this library provides the API in userland.
Installation
NPM
npm install --save @fluentui/react-context-selector
Yarn
yarn add @fluentui/react-context-selector
Usage
Getting started
import * as React from 'react';
import { createContext, useContextSelector, ContextSelector } from '@fluentui/react-context-selector';
interface CounterContextValue {
count1: number;
count2: number;
incrementCount1: () => void;
incrementCount2: () => void;
}
const CounterContext = createContext<CounterContextValue>({} as CounterContextValue);
const CounterProvider = CounterContext.Provider;
const useCounterContext = <T,>(selector: ContextSelector<CounterContextValue, T>) =>
useContextSelector(CounterContext, selector);
const Counter1 = () => {
const count1 = useCounterContext(context => context.count1);
const increment = useCounterContext(context => context.incrementCount1);
return <button onClick={increment}>Counter 1: {count1}</button>;
};
const Counter2 = () => {
const count2 = useCounterContext(context => context.count2);
const increment = useCounterContext(context => context.incrementCount2);
return <button onClick={increment}>Counter 2: {count2}</button>;
};
export default function App() {
const [state, setState] = React.useState({ count1: 0, count2: 0 });
const incrementCount1 = React.useCallback(() => setState(s => ({ ...s, count1: s.count1 + 1 })), [setState]);
const incrementCount2 = React.useCallback(() => setState(s => ({ ...s, count2: s.count2 + 1 })), [setState]);
return (
<div className="App">
<CounterProvider
value={{
count1: state.count1,
count2: state.count2,
incrementCount1,
incrementCount2,
}}
>
<Counter1 />
<Counter2 />
</CounterProvider>
</div>
);
}
useHasParentContext
This helper hook will allow you to know if a component is wrapped by a context selector provider
const Foo = () => {
const isWrappedWithContext = useHasParentContext(CounterContext);
if (isWrappedWithContext) {
return <div>I am inside context selector provider</div>;
} else {
return <div>I can only use default context value</div>;
}
};
Technical memo
React context by nature triggers propagation of component re-rendering if a value is changed. To avoid this, this library uses undocumented feature of calculateChangedBits
. It then uses a subscription model to force update when a component needs to re-render.
Limitations
- In order to stop propagation,
children
of a context provider has to be either created outside of the provider or memoized with React.memo
. <Consumer />
components are not supported.- The stale props issue can't be solved in userland. (workaround with try-catch)
Related projects
The implementation is heavily inspired by: